<!doctype html>
<html>
    <head>
        <title>{#NAV_LIVE}</title>
        {#HTML_HEADER}
        <meta name="apple-mobile-web-app-capable" content="yes">
    </head>
    <body>
        {#HTML_NAV}
        <div id="wrapper">
            <div id="content">
                <div id="live"></div>
                <p>{#EVERY} <span id="refresh"></span> {#UPDATE_SECS}</p>
            </div>
        </div>
        {#HTML_FOOTER}
        <script type="text/javascript">
            var exeOnce = true;
            var units, ivEn;
            var mIvHtml = [];
            var mNum = 0;
            var total = Array(6).fill(0);
            var tPwrAck;
            var totalsRendered = false

            function getErrStr(code) {
                if("ERR_AUTH") return "{#ERR_AUTH}"
                if("ERR_INDEX") return "{#ERR_INDEX}"
                if("ERR_UNKNOWN_CMD") return "{#ERR_UNKNOWN_CMD}"
                if("ERR_LIMIT_NOT_ACCEPT") return "{#ERR_LIMIT_NOT_ACCEPT}"
                if("ERR_UNKNOWN_CMD") return "{#ERR_AUTH}"
                return "n/a"
            }

            function parseGeneric(obj) {
                if(true == exeOnce){
                    parseNav(obj)
                    parseESP(obj)
                    parseTitle(obj)
                }
                parseRssi(obj)
            }

            function numBig(val, unit, des) {
                return ml("div", {class: "col-6 col-sm-4 a-c"}, [
                    ml("div", {class: "row"},
                        ml("div", {class: "col"}, [
                            ml("span", {class: "fs-5 fs-md-4"}, String(Math.round(val * 100) / 100)),
                            ml("span", {class: "fs-6 fs-md-7 mx-1"}, unit)
                        ])),
                    ml("div", {class: "row"},
                        ml("div", {class: "col"},
                            ml("span", {class: "fs-9 px-1"}, des)
                        )
                    )
                ]);
            }

            function numMid(val, unit, des, opt={class: "row"}) {
                return ml("div", {class: "col-6 col-sm-4 col-md-3 mb-2"}, [
                    ml("div", opt,
                        ml("div", {class: "col"}, [
                            ml("span", {class: "fs-6"}, String(Math.round(val * 100) / 100)),
                            ml("span", {class: "fs-8 mx-1"}, unit)
                        ])
                    ),
                    ml("div", {class: "row"},
                        ml("div", {class: "col"},
                            ml("span", {class: "fs-9"}, des)
                        )
                    )
                ]);
            }

            function totals() {
                for(var i = 0; i < 6; i++) {
                    total[i] = Math.round(total[i] * 100) / 100;
                }
                totalsRendered = true

                return ml("div", {class: "row mt-3 mb-5"},
                    ml("div", {class: "col"}, [
                        ml("div", {class: "p-2 total-h"},
                            ml("div", {class: "row"},
                                ml("div", {class: "col mx-2 mx-md-1"}, "{#TOTAL}")
                            ),
                        ),
                        ml("div", {class: "p-2 total-bg"}, [
                            ml("div", {class: "row"}, [
                                numBig(total[0], "W", "{#AC_POWER}"),
                                numBig(total[1], "Wh", "{#YIELD_DAY}"),
                                numBig(total[2], "kWh", "{#YIELD_TOTAL}")
                            ]),
                            ml("div", {class: "hr"}),
                            ml("div", {class: "row"}, [
                                numMid(total[3], "W", "{#MAX_POWER}"),
                                numMid(total[4], "W", "{#DC_POWER}"),
                                numMid(total[5], "var", "{#REACTIVE_POWER}")
                            ])
                        ])
                    ])
                );
            }
            function ivHead(obj) {
                if(0 != obj.status) { // only add totals if inverter is online
                    total[0] += obj.ch[0][2]; // P_AC
                    total[4] += obj.ch[0][8]; // P_DC
                    total[5] += obj.ch[0][10]; // Q_AC
                }
                total[1] += obj.ch[0][7]; // YieldDay
                total[2] += obj.ch[0][6]; // YieldTotal

                var t = span("&nbsp;&deg;C");
                var clh  = (0 == obj.status) ? "iv-h-dis" : "iv-h";
                var clbg = (0 == obj.status) ? "iv-bg-dis" : "iv-bg";
                var pwrLimit = "n/a";

                if(65535 != obj.power_limit_read) {
                    pwrLimit = obj.power_limit_read + "&nbsp;%";
                    if(0 != obj.max_pwr)
                        pwrLimit += ", " + (obj.max_pwr * obj.power_limit_read / 100).toFixed(1) + "&nbsp;W";
                }

                var maxAcPwrDate = toIsoDateStr(new Date(obj.ts_max_ac_pwr * 1000))
                var maxTempDate = toIsoDateStr(new Date(obj.ts_max_temp * 1000))
                return ml("div", {class: "row mt-2"},
                    ml("div", {class: "col"}, [
                        ml("div", {class: "p-2 " + clh},
                            ml("div", {class: "row"}, [
                                ml("div", {class: "col mx-2 mx-md-1"}, ml("span", { class: "pointer", onclick: function() {
                                    getAjax("/api/inverter/version/" + obj.id, parseIvVersion);
                                }}, obj.name)),
                                ml("div", {class: "col a-c", onclick: function() {limitModal(obj)}}, [
                                    ml("span", {class: "d-none d-sm-block pointer"}, "{#ACTIVE_POWER_CONTROL}: " + pwrLimit),
                                    ml("span", {class: "d-block d-sm-none pointer"}, "{#APC}: " + pwrLimit)
                                ]),
                                ml("div", {class: "col a-c"}, ml("span", { class: "pointer", onclick: function() {
                                    getAjax("/api/inverter/alarm/" + obj.id, parseIvAlarm);
                                }}, ("{#ALARMS}: " + obj.alarm_cnt))),
                                ml("div", {class: "col a-r mx-2 mx-md-1 tooltip", data: (obj.ch[0][12] + t.innerText + "\n" + maxTempDate)}, String(obj.ch[0][5].toFixed(1)) + t.innerText)
                            ])
                        ),
                        ml("div", {class: "p-2 " + clbg}, [
                            ml("div", {class: "row"},[
                                numBig(obj.ch[0][2], "W", "{#AC_POWER}"),
                                numBig(obj.ch[0][7], "Wh", "{#YIELD_DAY}"),
                                numBig(obj.ch[0][6], "kWh", "{#YIELD_TOTAL}")
                            ]),
                            ml("div", {class: "hr"}),
                            ml("div", {class: "row mt-2"},[
                                numMid(obj.ch[0][11], "W", "{#MAX_AC_POWER}", {class: "row tooltip", data: maxAcPwrDate}),
                                numMid(obj.ch[0][8], "W", "{#DC_POWER}"),
                                numMid(obj.ch[0][0], "V", "{#AC_VOLTAGE}"),
                                numMid(obj.ch[0][1], "A", "{#AC_CURRENT}"),
                                numMid(obj.ch[0][3], "Hz", "{#FREQUENCY}"),
                                numMid(obj.ch[0][9], "%", "{#EFFICIENCY}"),
                                numMid(obj.ch[0][10], "var", "{#REACTIVE_POWER}"),
                                numMid(obj.ch[0][4], "", "{#POWER_FACTOR}")
                            ])
                        ])
                    ])
                );
            }

            function numCh(val, unit, des) {
                return ml("div", {class: "col-12 col-sm-6 col-md-12 mb-2"}, [
                    ml("div", {class: "row"},
                        ml("div", {class: "col"}, [
                            ml("span", {class: "fs-6 fs-md-7"}, String(Math.round(val * 100) / 100)),
                            ml("span", {class: "fs-8 mx-2"}, unit)
                        ])),
                    ml("div", {class: "row"},
                        ml("div", {class: "col"},
                            ml("span", {class: "fs-9"}, des)
                        )
                    )
                ]);
            }

            function ch(status, name, vals) {
                var clh  = (0 == status) ? "iv-h-dis" : "ch-h";
                var clbg = (0 == status) ? "iv-bg-dis" : "ch-bg";
                return ml("div", {class: "col-6 col-md-3 mt-2"}, [
                    ml("div", {class: "p-2 a-c " + clh}, name),
                    ml("div", {class: "p-2 " + clbg}, [
                        ml("div", {class: "row"}, [
                            numCh(vals[2], units[2], "{#DC_POWER}"),
                            numCh(vals[6], units[2], "{#MAX_POWER}"),
                            numCh(vals[5], units[5], "{#IRRADIATION}"),
                            numCh(vals[3], units[3], "{#YIELD_DAY}"),
                            numCh(vals[4], units[4], "{#YIELD_TOTAL}"),
                            numCh(vals[0], units[0], "{#DC_VOLTAGE}"),
                            numCh(vals[1], units[1], "{#DC_CURRENT}")
                        ])
                    ])
                ]);
            }

            function tsInfo(obj) {
                var ageInfo = "{#LAST_RECEIVED}: ";
                if(obj.ts_last_success > 0) {
                    var date = new Date(obj.ts_last_success * 1000);
                    ageInfo += toIsoDateStr(date);
                }
                else
                    ageInfo += "{#NOTHING_RECEIVED}";

                if(obj.rssi > -127) {
                    if(obj.generation < 2)
                        ageInfo += " (RSSI: " + ((obj.rssi == -64) ? "&gt;=" : "&lt;") + " -64&nbsp;dBm)";
                    else {
                        if(obj.rssi == 0)
                            obj.rssi = "--";
                        ageInfo += " (RSSI: " + obj.rssi + "&nbsp;dBm)";
                    }
                }

                return ml("div", {class: "mb-5"}, [
                    ml("div", {class: "row p-1 ts-h mx-2"},
                        ml("div", {class: "col"}, "")
                    ),
                    ml("div", {class: "row p-2 ts-bg mx-2"},
                        ml("div", { class: "pointer col mx-2", onclick: function() {
                            getAjax("/api/inverter/radiostat/" + obj.id, parseIvRadioStats);
                        }}, ageInfo)
                    )
                ]);
            }

            function parseIv(obj) {
                mNum++;

                var chn = [];
                for(var i = 1; i < obj.ch.length; i++) {
                    var name = obj.ch_name[i];
                    if(name.length == 0)
                        name = "CHANNEL " + i;
                    if(obj.ch_max_pwr[i] > 0) // show channel only if max mod pwr
                        chn.push(ch(obj.status, name, obj.ch[i]));
                }
                mIvHtml.push(
                    ml("div", {}, [
                        ivHead(obj),
                        ml("div", {class: "row mb-2"}, chn),
                        tsInfo(obj)
                    ])
                );

                for(var i = obj.id + 1; i < ivEn.length; i++) {
                    if((i != ivEn.length) && ivEn[i]) {
                        getAjax("/api/inverter/id/" + i, parseIv);
                        return
                    }
                }

                if(mNum > 1) {
                    if(!totalsRendered)
                        mIvHtml.unshift(totals());
                }
                document.getElementById("live").replaceChildren(...mIvHtml);
            }

            function parseIvAlarm(obj) {
                var html = [];
                var offs = new Date().getTimezoneOffset() * -60;
                html.push(
                    ml("div", {class: "row"}, [
                        ml("div", {class: "col"}, ml("strong", {}, "{#EVENT}")),
                        ml("div", {class: "col"}, ml("strong", {}, "ID")),
                        ml("div", {class: "col"}, ml("strong", {}, "Start")),
                        ml("div", {class: "col"}, ml("strong", {}, "{#END}"))
                    ])
                );

                for(a of obj.alarm) {
                    if(a.code != 0) {
                        html.push(
                            ml("div", {class: "row"}, [
                                ml("div", {class: "col mt-3"}, String(a.str)),
                                ml("div", {class: "col mt-3"}, String(a.code)),
                                ml("div", {class: "col mt-3"}, String(toIsoTimeStr(new Date((a.start + offs) * 1000)))),
                                ml("div", {class: "col mt-3"}, (a.end == 0) ? "-" : String(toIsoTimeStr(new Date((a.end + offs) * 1000))))
                            ])
                        );
                    }
                }
                modal("{#ALARMS_MODAL}: " + obj.iv_name, ml("div", {}, html));
            }

            function parseIvVersion(obj) {
                var model;
                switch(obj.generation) {
                    case 0: model = "MI-"; break;
                    case 1: model = "HM-"; break;
                    case 2: model = "HMS-"; break;
                    case 3: model = "HMT-"; break;
                    default: model = "???-"; break;
                }
                model += String(obj.max_pwr) + " ({#SERIAL}: " + obj.serial + ")";


                var html = ml("table", {class: "table"}, [
                    ml("tbody", {}, [
                        tr("Model", model),
                        tr("Firmware Version / Build", String(obj.fw_ver) + " (build: " + String(obj.fw_date) + " " + String(obj.fw_time) + ")"),
                        tr("Hardware Version / Build", (obj.hw_ver/100).toFixed(2) + " (build: " + String(obj.prod_cw) + "/" + String(obj.prod_year) + ")"),
                        tr("{#HW_NUMBER}", obj.part_num.toString(16)),
                        tr("Bootloader Version", (obj.boot_ver/100).toFixed(2)),
                        tr("Grid Profile", ml("input", {type: "button", value: "{#BTN_SHOW}", class: "btn", onclick: function() {
                            modalClose();
                            getAjax("/api/inverter/grid/" + obj.id, showGridProfile);
                        }}, null))
                    ])
                ])
                modal("{#INV_INFO}: " + obj.name, ml("div", {}, html))
            }

            function getGridValue(g) {
                var val = (parseInt(g.grid.substring(g.offs*3, g.offs*3+2), 16) * 256)
                    + parseInt(g.grid.substring(g.offs*3+3, g.offs*3+5), 16)
                g.offs += 2
                return val
            }

            function getGridIdentifier(g) {
                return "0x" + getGridValue(g).toString(16).padStart(4, '0')
            }

            function getGridType(t, id) {
                for(e of t) {
                    if(undefined !== e[id])
                        return e[id]
                }
                return null
            }

            function parseGridGroup(g) {
                var id = getGridIdentifier(g)
                var type = getGridType(g.info.grp_codes, id.substring(0, 4))
                var content = []
                content.push(ml("div", {class: "row"},
                    ml("div", {class: "col head p-2 mt-3"},
                        ml("div", {class: "col a-c"}, type + " (Code " + id + ")")
                    )
                ))
                content.push(ml("div", {class: "row my-2"}, [
                    ml("div", {class: "col-4"}, ml("b", {}, "Name")),
                    ml("div", {class: "col-3"}, ml("b", {}, "{#VALUE}")),
                    ml("div", {class: "col-3"}, ml("b", {}, "{#RANGE}")),
                    ml("div", {class: "col-2"}, ml("b", {}, "{#DEFAULT}"))
                ]))
                for(e of g.info.group) {
                    if(Array.isArray(e[id])) {
                        for(e of e[id]) {
                            var v = String(getGridValue(g) / e.div);
                            var vt = (v !== String(e.def)) ? "b" : "span";
                            content.push(ml("div", {class: "row mt-2"}, [
                                ml("div", {class: "col-4"}, e.name),
                                ml("div", {class: "col-3"}, ml(vt, {}, v + ((undefined !== e.unit) ? " [" + e.unit + "]" : ""))),
                                ml("div", {class: "col-3"}, (undefined !== e.min) ? (e.min + " - " + e.max) : "n/a"),
                                ml("div", {class: "col-2"}, String(e.def))
                            ]))
                        }
                    }
                }

                return ml("div", {class: "col"}, [...content])
            }

            function showGridProfile(obj) {
                getJSON("/grid_info.json").then(data => {
                    var glob = {offs:0, grid:obj.grid, info: data}
                    var content = [];
                    var g = getGridType(glob.info.type, getGridIdentifier(glob))
                    var v = getGridValue(glob);
                    if(null === g) {
                        if(0 == obj.grid.length) {
                            content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "{#PROFILE_NOT_READ}"))))
                        } else {
                            content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("h5", {}, "{#UNKNOWN_PROFILE}"))))
                            content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "{#OPEN_ISSUE}."))))
                        }
                    } else {
                        content.push(ml("div", {class: "row"},
                            ml("div", {class: "col my-3"}, ml("h5", {}, g + " (Version " + (Math.round(v / 0x1000)) + "." + (Math.round((v & 0x0ff0) / 0x10)) + "." + (v & 0x0F) + ")"))
                        ))

                        while((glob.offs*3) < glob.grid.length) {
                            content.push(parseGridGroup(glob))
                        }
                    }
                    if(0 != obj.grid.length)
                        content.push(ml("div", {class: "row"}, ml("div", {class: "col my-2"}, ml("pre", {}, obj.grid))))

                    modal("{#PROFILE_MODAL}: " + obj.name, ml("div", {}, ml("div", {class: "col mb-2"}, [...content])))
                })
            }


            function parseIvRadioStats(obj) {
                var html = ml("table", {class: "table"}, [
                    ml("tbody", {}, [
                        tr2(["{#TX_COUNT}", obj.tx_cnt, ""]),
                        tr2(["{#RX_SUCCESS}", obj.rx_success, String(Math.round(obj.rx_success / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]),
                        tr2(["{#RX_FAIL}", obj.rx_fail, String(Math.round(obj.rx_fail / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]),
                        tr2(["{#RX_NO_ANSWER}", obj.rx_fail_answer, String(Math.round(obj.rx_fail_answer / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]),
                        tr2(["{#RX_FRAGMENTS}", obj.frame_cnt, ""]),
                        tr2(["{#TX_RETRANSMITS}", obj.retransmits, ""]),
                        tr2(["{#INV_LOSS_RATE}", "{#LOST_1} " + obj.ivLoss + " {#LOST_2} " + obj.ivSent + " {#LOST_3}", String(Math.round(obj.ivLoss / obj.ivSent * 10000) / 100) + "&nbsp;%"]),
                        tr2(["{#DTU_LOSS_RATE}", "{#LOST_1} " + obj.dtuLoss + " {#LOST_2} " + obj.dtuSent + " {#LOST_3}", String(Math.round(obj.dtuLoss / obj.dtuSent * 10000) / 100) + "&nbsp;%"])
                    ])
                ])
                modal("{#RADIO_STAT_MODAL}: " + obj.name, ml("div", {}, html))
            }

            function limitModal(obj) {
                var opt = [["pct", "%"], ["watt", "W"]];
                var html = ml("div", {}, [
                    ml("div", {class: "row mb-3"}, [
                        ml("div", {class: "col-12 col-sm-5 my-2"}, "{#LIMIT_VALUE}"),
                        ml("div", {class: "col-8 col-sm-5"}, ml("input", {name: "limit", type: "number", step: "0.1", min: 1}, "")),
                        ml("div", {class: "col-4 col-sm-2"}, sel("type", opt, "pct"))
                    ]),
                    ml("div", {class: "row mb-3"}, [
                        ml("div", {class: "col-8 col-sm-5"}, "{#KEEP_LIMIT}"),
                        ml("div", {class: "col-4 col-sm-7"}, ml("input", {type: "checkbox", name: "keep"}))
                    ]),
                    ml("div", {class: "row my-3"},
                        ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "{#BTN_APPLY}", class: "btn", onclick: function() {
                            applyLimit(obj.id);
                        }}, null))
                    ),
                    ml("div", {class: "row my-4"}, [
                        ml("div", {class: "col-12 col-sm-5 my-2"}, "{#CONTROL}"),
                        ml("div", {class: "col col-sm-7 a-r"}, [
                            ml("input", {type: "button", value: "{#RESTART}", class: "btn", onclick: function() {
                                applyCtrl(obj.id, "restart");
                            }}, null),
                            ml("input", {type: "button", value: "{#TURN_OFF}", class: "btn mx-1", onclick: function() {
                                applyCtrl(obj.id, "power", 0);
                            }}, null),
                            ml("input", {type: "button", value: "{#TURN_ON}", class: "btn", onclick: function() {
                                applyCtrl(obj.id, "power", 1);
                            }}, null)
                        ])
                    ]),
                    ml("div", {class: "row mt-1"}, [
                        ml("div", {class: "col-12 col-sm-5 my-2"}, "{#RESULT}"),
                        ml("div", {class: "col-sm-7 my-2"}, ml("span", {name: "pwrres"}, "-"))
                    ])
                ]);
                modal("{#POWER_LIMIT_MODAL}: " + obj.name, html);
            }

            function applyLimit(id) {
                var cmd = "limit_";
                if(!document.getElementsByName("keep")[0].checked)
                    cmd += "non";
                cmd += "persistent_";
                if(document.getElementsByName("type")[0].value == "pct")
                    cmd += "relative";
                else
                    cmd += "absolute";

                var val = document.getElementsByName("limit")[0].value;
                if(isNaN(val))
                    val = 100;

                var obj = new Object();
                obj.id = id
                obj.token = "*"
                obj.cmd = cmd
                obj.val = val
                getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj))
            }

            function applyCtrl(id, cmd, val=0) {
                var obj = new Object();
                obj.id  = id
                obj.token = "*"
                obj.cmd = cmd
                obj.val = val
                getAjax("/api/ctrl", ctrlCb2, "POST", JSON.stringify(obj))
            }

            function ctrlCb(obj) {
                var e = document.getElementsByName("pwrres")[0];
                if(obj.success) {
                    e.innerHTML = "{#CMD_RECEIVED_WAIT_ACK}";
                    tPwrAck = window.setInterval("getAjax('/api/inverter/pwrack/" + obj.id + "', updatePwrAck)", 1000);
                }
                else
                    e.innerHTML = "{#ERROR}: " + getErrStr(obj.error);
            }

            function ctrlCb2(obj) {
                var e = document.getElementsByName("pwrres")[0];
                if(obj.success)
                    e.innerHTML = "{#COMMAND_RECEIVED}";
                else
                    e.innerHTML = "{#ERROR}: " + getErrStr(obj.error);
            }

            function updatePwrAck(obj) {
                if(!obj.ack)
                    return;
                var e = document.getElementsByName("pwrres")[0];
                clearInterval(tPwrAck);
                if(null == e)
                    return;
                e.innerHTML = "{#INV_ACK}";
            }

            function parse(obj) {
                if(null != obj) {
                    parseGeneric(obj["generic"]);
                    units = Object.assign({}, obj["fld_units"]);
                    ivEn = Object.values(Object.assign({}, obj["iv"]));
                    mIvHtml = [];
                    mNum = 0;
                    totalsRendered = false
                    total.fill(0);
                    total[3] = obj.max_total_pwr
                    for(var i = 0; i < obj.iv.length; i++) {
                        if(obj.iv[i]) {
                            getAjax("/api/inverter/id/" + i, parseIv);
                            break;
                        }
                    }
                    if(obj.refresh < 5)
                        obj.refresh = 5;
                    document.getElementById("refresh").innerHTML = obj.refresh;
                    if(true == exeOnce) {
                        window.setInterval("getAjax('/api/live', parse)", obj.refresh * 1000);
                        exeOnce = false;
                    }
                }
                else
                    document.getElementById("refresh").innerHTML = "n/a";
            }

            getAjax("/api/live", parse);
        </script>
    </body>
</html>
